/* @flow */

import { hasOwn } from 'shared/util'
import { warn, hasSymbol } from '../util/index'
import { defineReactive, toggleObserving } from '../observer/index'

/**
 * provide 是函数，执行它，不是函数，则返回
 * @param {*} vm 
 */
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

export function initInjections (vm: Component) {
  // 自底向上搜索可用的注入内容，将结果保存到 result 变量中
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        // 调用 defineReactive 将它们设置到 Vue.js 实例上
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

/**
 * 自底向上搜索可用的注入内容
 * 
 * 读出用户在当前组件中设置的 inject 的 key，如何循环 key，将每一个 key 从当前组件起，不断向父组件查找是否有值，找到了就停止循环，最后将所有 key 对应的值一起返回即可
 * @param {*} inject 
 * @param {*} vm 
 * @returns 
 */
export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    // 使用 Reflect.ownKeys 可用返回所有类型的属性名，使用 filter 将不可枚举的属性过滤掉
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // #6574 in case the inject object is observed...
      if (key === '__ob__') continue
      // 得到 provide 的源属性
      const provideKey = inject[key].from
      // 最开始让 source = 当前组件实例
      let source = vm
      // 如果能在当前组件实例的 _provided 上找到对应的值，跳出循环，返回值
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        // 否则，去当前组件实例的父组件上面接着寻找
        source = source.$parent
      }
      // injected 是支持默认值的
      // 在所有祖先组件实例中都搜索不到注入内容时，将使用默认值
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          // 默认值是支持函数的，如果默认值是函数，那么就执行函数，将函数的返回值设置为 result
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result
  }
}
